package client.neo4j;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import entities.PlainAddable;
import entities.Enterprise;
import entities.Person;
import org.eclipse.collections.impl.block.factory.Predicates;
import org.neo4j.driver.v1.Session;
import org.neo4j.driver.v1.StatementResult;
import utils.Neo4jConnectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class NodeOps {


    public static void addEnterpriseNode(Session session, Enterprise enterprise){
        String sql = " MERGE (:" + enterprise + ") ";
        session.run(sql);
    }

    public static void addPersonNode(Session session, Person person){
        String sql = " MERGE (:" + person + ")";
        session.run(sql);
    }

    public static void addPlainNode(Session session, PlainAddable node) {
        String sql = " MERGE (:" + node +  " )";
        session.run(sql);
    }

    public static void addBaseLawNode(Session session, String lawTitle, String content,
                                  String belongsLaw, String publish_date, String implement_date){
        String sql = " MERGE (l:Law {name: '"+lawTitle+"' }) SET l.level=0, " +
                " l.content = '" +content+"', l.belongs_law= '"+belongsLaw+"'," +
                " l.publish_date= '"+publish_date+"', l.implement_date='"+implement_date+"'" ;
        session.run(sql);
    }

    public static void batchAddEnterprise(Session session, List<Map<String, String>> nodes) {
        //session.run("CREATE CONSTRAINT ON (e:Enterprise) ASSERT e.enterpriseID IS UNIQUE");
        //session.run("CREATE CONSTRAINT ON (e:Enterprise) ASSERT e.enterpriseName IS UNIQUE");

        String sql = " UNWIND $nodes as n" +
                " MERGE (e:Enterprise {enterpriseID: n.enterpriseID}) SET e = n";
        Map<String, Object> params = new HashMap<>();
        params.put("nodes", nodes);
        session.run(sql, params);
    }



    public static void addNodeApoc(Session session, List<String> labels, List<Map<String, String>> props) {
        Map<String, Object> params = new HashMap<>();
        params.put("node_labels", labels);
        params.put("node_props", props);
        String sql = " CALL apoc.create.nodes($node_labels, $node_props)";
        session.run(sql, params);
    }

    public static void batchAddNodeApoc(Session session, List<String> labels, List<Map<String, String>> props) {
        String sql = " CALL apoc.periodic.iterate( ' UNWIND $node_props as props  RETURN  props', " +
                " ' CALL apoc.create.node($node_labels, props) yield node RETURN count(*) ', " +
                " {batchSize:2000, parallel:true, iterateList:true,  params: {node_props:$node_props, node_labels:$node_labels}} ) ";
        Map<String, Object> params = new HashMap<>();
        params.put("node_labels", labels);
        params.put("node_props", props);
        session.run(sql, params);
    }

    public static void batchAddJudgement(Session session, List<Map<String, String>> props){
        String sql = " CALL apoc.periodic.iterate( ' UNWIND $node_props as props  RETURN  props', " +
                " ' MERGE (j:Judgement {doc_id: props.doc_id}) SET j = props ', " +
                " {batchSize:1000, iterateList:true,  params: {node_props:$node_props}} ) ";
        session.run(sql, ImmutableMap.of("node_props", props));
    }


    public static void batchUpdateJudgement(Session session, List<Map<String, String>> props) {
        String sql = " CALL apoc.periodic.iterate(" +
                " ' UNWIND $node_props as props  RETURN  props'," +
                " ' MERGE (j:Judgement {doc_id: props.doc_id}) ON MATCH SET j += props', " +
                " {batchSize:1000, iterateList:true,  params: {node_props:$node_props} }) ";
        session.run(sql, ImmutableMap.of("node_props", props));
    }


    // low efficiency
    public static void addPersonNodeWithParams(Session session, Person person){
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> params = new HashMap<>();
        Map<String, Object> p1 = mapper.convertValue(person, Map.class);
        params.put("p1", p1);
        String sql = " MERGE (p:Person {name: $p1.name}) SET p = $p1";
        session.run(sql, params);
    }

    public static void updateEnterpriseByMap(Session session, String qualifierName, String qualifierValue, Map<String, String> props){
        Map<String, Object> params = new HashMap<>();
        params.put("props", props);
        String sql = " MATCH (e:Enterprise) " +
                " WHERE e." + qualifierName + " = '" + qualifierValue + "' " +
                " SET e += $props"  ;
        session.run(sql, params);
    }


    public static StatementResult getRandomNeighbourNodes(Session session, int limit){
        String sql = " MATCH (m) WHERE  rand() < 0.20 " +
                " WITH m LIMIT " + limit +
                " MATCH (m) -- (n) return m,n ";
        return session.run(sql);
    }
    
    public static StatementResult getRandomNodes(Session session, int limit) {
        String sql = " MATCH (m) WHERE rand() < 0.20 " +
                " WITH m LIMIT " + limit +
                " RETURN m ";
        return session.run(sql);
    }

    public static void addContentNode(Session session, Map<String,Object> prop) {
        String sql = " CREATE (c:Content) SET c = $prop";
        session.run(sql, ImmutableMap.of("prop", prop));
    }

    public static void deleteContentNode(Session session, String doc_id) {
        String sql = " MATCH (c:Content{doc_id: $doc_id}) DELETE c";
        session.run(sql, ImmutableMap.of("doc_id", doc_id));
    }

    // UPDATE:  update query index to LabelContentIndex
    public static Double queryFulltextContent(Session session, String queryWord, String doc_id) {
        String sql = " CALL db.index.fulltext.queryNodes('LabelContentIndex', $queryWord) YIELD node, score" +
                " WITH node, score" +
                " WHERE node.doc_id = $doc_id " +
                " RETURN score ";
        StatementResult statementResult =  session.run(sql, ImmutableMap.of("queryWord", queryWord, "doc_id", doc_id));
        if (statementResult.hasNext()){
            return statementResult.single().get("score").asDouble();
        } else
            return 0.0;

    }

    public static Map<String, Object> queryJudgementById(Session session, String doc_id){
        String sql = " MATCH (j:Judgement {doc_id: '"+doc_id+"'})  RETURN j";
        StatementResult statementResult = session.run(sql);
        if(statementResult.hasNext()){
            return statementResult.single().get("j").asNode().asMap();
        } else
            return new HashMap<>();
    }

    public static List<Map<String, Object>> queryContent(Session session, String queryWord, int num) {
        String sql = " CALL db.index.fulltext.queryNodes('ContentIndex', $queryWord) YIELD node, score" +
                " RETURN node LIMIT $num ";
        StatementResult statementResult =  session.run(sql, ImmutableMap.of("queryWord", queryWord, "num", num));
        return statementResult.stream()
                .filter(Predicates.notNull())
                .map(record -> record.get("node").asNode().asMap())
                .collect(Collectors.toList());
    }

    public static void addLabelContent(Session session, Map<String,Object> prop) {
        String sql = " MERGE (c:LabelContent{doc_id: $prop.doc_id}) SET c = $prop";
        session.run(sql, ImmutableMap.of("prop", prop));
    }

    public static void batchAddLabelContent(Session session, List<Map<String, Object>> props){
        String sql = " CALL apoc.periodic.iterate( ' UNWIND $node_props as props  RETURN  props', " +
                " ' MERGE (j:LabelContent {doc_id: props.doc_id}) SET j = props ', " +
                " {batchSize:1000, iterateList:true,  params: {node_props:$node_props}} ) ";
        session.run(sql, ImmutableMap.of("node_props", props));
    }

    public static void deleteLabelContent(Session session, String doc_id) {
        String sql = " MATCH (c:LabelContent{doc_id: $doc_id}) DELETE c";
        session.run(sql, ImmutableMap.of("doc_id", doc_id));
    }
}
